﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;

namespace ZerAx.Net
{
    public class UdpConnection : IConnection
    {
        private readonly byte[] buffer;
        private readonly RingMemoryStream readStream = new();

        private readonly Queue<Packet> msgToSend = new();

        private readonly Socket client;
        private readonly IPEndPoint target;

        private bool isConnecting = true;

        public UdpConnection(IPEndPoint remote) : this(new IPEndPoint(IPAddress.Any, 0), remote) { }

        public UdpConnection(IPEndPoint local, IPEndPoint remote)
        {
            client = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            client.Bind(local);
            client.Blocking = false;

            target = remote;
        }
        public bool IsConnecting => isConnecting;

        public void Disconnect()
        {
            Send(new Packet(Packet.TYPE_DISCONNECT));
        }

        public bool Poll(out Packet packet)
        {
            var ans = (packet = Packet.From(readStream)) != null;
            if (packet.typeId == Packet.TYPE_DISCONNECT)
            {
                isConnecting = false;
            }
            return ans;
        }

        public void Send(Packet packet)
        {
            msgToSend.Enqueue(packet);
        }

        public void Update()
        {
            if (!IsConnecting) return;

            if (client.Poll(0, SelectMode.SelectWrite))
            {
                while (msgToSend.Count > 0)
                {
                    client.SendTo(msgToSend.Dequeue().GetBytes(), target);
                }
            }

            if (client.Poll(0, SelectMode.SelectRead))
            {
                EndPoint ep = new IPEndPoint(0, 0);
                int len = client.ReceiveFrom(buffer, ref ep);
                if (ep != target) return;

                if (len <= 0)
                {
                    Disconnect();
                    return;
                }
                readStream.Seek(0, SeekOrigin.End);
                readStream.Flush();

                readStream.Write(buffer, 0, len);
            }
        }
    }
}
